package pin;

import java.io.*;
import java.net.*;
import java.math.BigInteger;

public class Packet
{
    //Variabele die de grootte van de packet bijhoudt
    int msgSize;
    //Variabele die de huidige leespositie van de packet bijhoudt
    int readPos;
    //Byte array waarin alle data (incl. header) komt te staan
    byte[] buffer;

    //Maximale grootte van een packet
    final int PACKET_MAXSIZE = 128;

    public Packet()
    {
        try
        {
            //De byte array initialiseren met de maximale grootte
            buffer = new byte[PACKET_MAXSIZE];
            reset();
        }
        catch(Exception e)
        {
            System.out.println("Exception while creating packet: " + e);
        }
    }

    /*
     * De grootte en leespositie initialiseren/resetten
     */
    public void reset()
    {
        msgSize = 0;
        readPos = 2;
    }

    /*
     * Controleren of de hoeveelheid bytes aan de packet kunnen worden toegevoegd.
     */
    public boolean canAdd(int size)
    {
        if(size + readPos < PACKET_MAXSIZE - 16)
            return true;
        else
            return false;
    }

    /*
     * De packet versturen naar de gewenste socket.
     * Ook wordt hier de packet grootte toegevoegd aan het begin
     * van de packet.
     */
    public void sendToServer(Socket socket) throws IOException
    {
        //De grootte van de bruikbare data in de eerste twee bytes opslaan
        buffer[0] = (byte)(msgSize);
        buffer[1] = (byte)(msgSize >> 8);

        //Alle data met RSA versleutelen
        RSAencrypt();

        BufferedOutputStream output = new BufferedOutputStream(socket.getOutputStream(),PACKET_MAXSIZE);

        //Alle data versturen
        output.write(buffer, 0, 128);
        output.flush();
    }

    /*
     * (Data van) een packet ontvangen van een socket.
     * Er wordt gebruik gemaakt van een "blocking" systeem. Dit houdt in dat
     * de applicatie bij deze functie blijft hangen totdat er een packet is
     * ontvangen. Het is daarom van belang dat deze functie in een aparte thread
     * wordt aangeroepen.
     */
    public boolean receiveFromServer(Socket socket) throws IOException
    {
        reset();
        try
        {
            BufferedInputStream input = new BufferedInputStream(socket.getInputStream(), PACKET_MAXSIZE);

            //Alle data inlezen (altijd een grootte van 128 bytes)
            input.read(buffer,0,128);

            //Alle data ontsleutelen
            RSAdecrypt();

            //De grootte van de bruikbare data uitlezen
            int datasize = buffer[0] | buffer[1] << 8;
            if(datasize <= 0)
                return false;
            else
                return true;
        }
        catch(IOException e)
        {
            System.out.println("Exception while receiving a packet: " + e);
            return false;
        }
    }

    /*
     * Een byte toevoegen aan de packet
     */
    public void addByte(int value)
    {
        if(!canAdd(1))
            return;

        buffer[readPos++] = (byte)(value);
        msgSize++;
    }

    /*
     * Een integer toevoegen aan de packet
     */
    public void addInt(int value)
    {
        if(!canAdd(2))
            return;

        buffer[readPos++] = (byte)(value);
        buffer[readPos++] = (byte)((value >> 8));

        msgSize += 2;
    }

    /*
     * Een long toevoegen aan de packet
     */
    public void addLong(long value)
    {
        if(!canAdd(4))
            return;

        buffer[readPos++] = (byte)(value);
        buffer[readPos++] = (byte)(value >>  8);
        buffer[readPos++] = (byte)(value >> 16);
        buffer[readPos++] = (byte)(value >> 24);
        msgSize += 4;
    }

    /*
     * Een string toevoegen aan de packet.
     * Hierbij wordt eerst de grootte van de string als integer toegevoegd
     * aan de packet. Vervolgens wordt elk character in de string als byte
     * toegevoegd.
     */
    public void addString(String string)
    {
        addInt(string.length());
        for(int i = 0 ; i < string.length() ; i++)
        {
            buffer[readPos++] = (byte)string.charAt(i);
        }
        msgSize += string.length();
    }

    /*
     * Een byte uitlezen
     */
    public byte readByte()
    {
        return buffer[readPos++];
    }

    /*
     * Een integer uitlezen
     */
    public int readInt()
    {
        int v = ((buffer[readPos] & 0xFF) |
                 ((buffer[readPos+1] & 0xFF) << 8));
        readPos += 2;
        return v;
    }

    /*
     * Een long uitlezen
     */
    public long readLong()
    {
        long v = ((buffer[readPos] & 0xFF) |
                 ((buffer[readPos+1] & 0xFF) << 8) |
                 ((buffer[readPos+2] & 0xFF) << 16) |
                 ((buffer[readPos+3] & 0xFF) << 24));

        readPos += 4;
        return v;
    }

    /*
     * Een string uitlezen.
     * Eerst wordt de lengte van de string uitgelezen als integer. Vervolgens
     * wordt elke character als byte uitgelezen.
     */
    public String readString()
    {
        String string = "";
        int stringlength = readInt();
        for(int i = 0 ; i < stringlength ; i++)
        {
            string = string+(char)buffer[readPos++];
        }
        return string;
    }

    /*
     * Alle data versleuten met de public key
     */
    public void RSAencrypt()
    {
        try
        {
            //public exponent
            BigInteger exponent = new BigInteger("65537");
            BigInteger modulus = new BigInteger("99379948379960362318133545416777134441212959077806925836047576484354847740695895722711224301417536998763774398137274853428838318449808212642812881950275506403998235108557228615154895317730035593809893475114123023601357966397062390155405161131857926830195384564871250195516504358455352038134156651572224747907");

            BigInteger raw = new BigInteger(1,buffer);
            BigInteger encrypted = raw.modPow(exponent, modulus);

            byte[] encrypteddata = encrypted.toByteArray();
            System.arraycopy(encrypteddata, 0, buffer, 0, 128);
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }

    /*
     * Alle data ontsleuten met de private key
     */
    public void RSAdecrypt()
    {
        try
        {
            //private exponent
            BigInteger exponent = new BigInteger("76593087762207575883682887208773869121651381098006131253776692375677302277836179455180187366901136667951817215464756593171653012266350501557722792732478062988392269942259024689285491648917186497396093013932723792609119268512250541915741005297805638487747749730364000190772904587668393429145565491707546972673");
            BigInteger modulus = new BigInteger("99379948379960362318133545416777134441212959077806925836047576484354847740695895722711224301417536998763774398137274853428838318449808212642812881950275506403998235108557228615154895317730035593809893475114123023601357966397062390155405161131857926830195384564871250195516504358455352038134156651572224747907");

            BigInteger data = new BigInteger(1,buffer);
            BigInteger raw = data.modPow(exponent, modulus);

            byte[] rawdata = raw.toByteArray();
            System.arraycopy(rawdata, 0, buffer, 0, 128);
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }
}